package com.atguigu.gmall.seckill.service.impl;


import com.atguigu.gmall.common.constants.RabbitMqConst;
import com.atguigu.gmall.common.constants.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.JsonUtil;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.common.util.UserAuthToUtil;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.model.to.SecKillQueueTo;
import com.atguigu.gmall.seckill.biz.LocalCache;
import com.atguigu.gmall.seckill.service.SecKillService;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author A
 * @version 1.0
 * @description: TODO
 * @date 2024/10/20 4:49
 */
@Slf4j
@Service
public class SecKillServiceImpl implements SecKillService {
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    LocalCache localCache;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Override
    public String getSeckillSkuIdStr(Long skuId) {

        //1.校验开始时间
        SeckillGoods detail = localCache.getSeckillGoodsDetail(skuId);
        if (!detail.getStartTime().before(new Date())){
            //秒杀还没开始
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START);
        }
        if (detail.getEndTime().before(new Date())){
            //秒杀已结束
            throw new GmallException(ResultCodeEnum.SECKILL_END);
        }
        //校验库存
        if (detail.getStockCount()<=0){
            //已售罄
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }

        Long userId = UserAuthToUtil.getUserAuthTo().getUserId();
        String date = DateUtil.formatDate(new Date());
        String encKey=date+skuId.toString()+userId.toString();
        //1.生成秒杀码,算法(日期+用户id+skuId)
        String encrypt = MD5.encrypt(encKey);

        //2.给redis中也存一份秒杀码
        String codeKey=RedisConst.SECKILL_CODE_CACHE_PREFIX+encrypt;
        redisTemplate.opsForValue().set(codeKey,"1",1, TimeUnit.DAYS);

        return encrypt;
    }
    //生成秒杀单
    @Override
    public void ajaxSeckillOrder(Long skuId, String skuIdStr) throws InterruptedException {
        Long userId = UserAuthToUtil.getUserAuthTo().getUserId();
        //继续前置校验
        //1.校验开始时间
        SeckillGoods detail = localCache.getSeckillGoodsDetail(skuId);
        if (!detail.getStartTime().before(new Date())){
            //秒杀还没开始
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START);
        }
        if (detail.getEndTime().before(new Date())){
            //秒杀已结束
            throw new GmallException(ResultCodeEnum.SECKILL_END);
        }
        //校验库存
        if (detail.getStockCount()<=0){
            //已售罄
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }
        //校验秒杀码
        String codeKey=RedisConst.SECKILL_CODE_CACHE_PREFIX+ skuIdStr;
        Boolean hasKey = redisTemplate.hasKey(codeKey);
        if (Boolean.FALSE.equals(hasKey)){
            //秒杀码错误,秒杀失败
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL);
        }
        //防止刷新页面多次生成秒杀单,默认是1
        Long increment = redisTemplate.opsForValue().increment(codeKey);
        if (increment>2){
            //啥也不干
            log.info("用户[{}]商品[{}]已经秒杀过了,不能再次秒杀",userId,skuId);
            return;
        }
        //本地缓存预扣库存,先过滤大部分请求
        int newStock = detail.getStockCount() - 1;
        detail.setStockCount(newStock);
        if (newStock<=0){
            //表示已经被抢空
            throw new GmallException((ResultCodeEnum.SECKILL_FINISH));
        }
        //尝试扣库存,扣redis信号量
        String semaphoreKey = RedisConst.SECKILL_GOODS_CACHE_PREFIX + DateUtil.formatDate(new Date()) + ":" + skuId;
        RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
        //阻塞式扣信号
        //semaphore.acquire(1);
        boolean tryAcquire = semaphore.tryAcquire(1);
        if (tryAcquire){
            //扣成功,可以买这个商品
            //业务麻烦,直接发送消息,提高用户体验,使用mq
            SecKillQueueTo secKillQueueTo = new SecKillQueueTo(skuId,userId,skuIdStr,DateUtil.formatDate(new Date()));
            rabbitTemplate.convertAndSend(RabbitMqConst.SECKILL_EVENT_EXCHANGE,RabbitMqConst.SECKILL_RK_STOCK, JsonUtil.obj2Json(secKillQueueTo));

        }else {
            //扣失败
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }



    }
    //检查订单状态
    @Override
    public ResultCodeEnum checkSeckillOrder(Long skuId) {
        Long userId = UserAuthToUtil.getUserAuthTo().getUserId();
        String tempKey= RedisConst.SECKILL_TEMP_ORDER_PREFIX+userId+":"+DateUtil.formatDate(new Date())+":"+skuId;
        String json = redisTemplate.opsForValue().get(tempKey);
        if (StringUtils.isEmpty(json)){
            //走到这里,表明已经发过请求了
            return ResultCodeEnum.SECKILL_RUN;
        }else {
            if ("boom".equals(json)){
                //表明扣库存了,但是没有了
                return ResultCodeEnum.SECKILL_FINISH;
            }else {

                OrderInfo orderInfo = JsonUtil.json2Obj(json, new TypeReference<OrderInfo>() {
                });
                if (orderInfo.getId()==null){
                    //表明抢到了,待支付
                    return ResultCodeEnum.SECKILL_SUCCESS;
                }else {
                    //表明已支付,下单成功
                    return ResultCodeEnum.SECKILL_ORDER_SUCCESS;
                }
            }
        }

    }

    @Override
    public OrderInfo getSecKillOrderInfoFromRedis(Long skuId) {
        Long userId = UserAuthToUtil.getUserAuthTo().getUserId();
        String tempKey= RedisConst.SECKILL_TEMP_ORDER_PREFIX+userId+":"+DateUtil.formatDate(new Date())+":"+skuId;
        String json = redisTemplate.opsForValue().get(tempKey);
        if (!StringUtils.isEmpty(json)&&!"boom".equals(json)){
            return JsonUtil.json2Obj(json, new TypeReference<OrderInfo>() {
            });
        }
        return null;
    }


}
